#include "tm_maya_plugin.h"
#include "tm_stdout_maya_export_target.h"

#include "tm_maya_export.h"

#include <maya/MQuaternion.h>
#include <maya/MAnimControl.h>

TStdoutMayaExportTarget StdExportTarget;

TMayaExportTriangle GetTriangle(MItMeshPolygon& itFace, int v1, int v2, int v3)
{
	TMayaExportTriangle tri;

	int points[] = {v1, v2, v3};
			
	for (int i=0; i < 3; ++i)
	{
		//get this tri's uvs
		int uv_index;
		itFace.getUVIndex(points[i], uv_index);

		//get vertex and normal indices
		int vert_index = itFace.vertexIndex(points[i]);
		int normal_index = itFace.normalIndex(points[i]);
		int tangent_index = itFace.tangentIndex(points[i]);

		tri.vertex_idx[i] = vert_index;
		tri.normal_idx[i] = normal_index;
		tri.uv_idx[i] = uv_index;
		tri.tangent_idx[i] = tangent_index;
	}

	return tri;
}

void ExportPolyMesh(TBaseMayaExportTarget& target, MFnMesh& Mesh)
{
	if (g_TmVerbosity >= 1)
		cout << "Found mesh object: " << Mesh.name().asChar() << endl;

	TMayaExportMesh ExportMesh;

	//get vertex positions
	Mesh.getPoints(ExportMesh.Vertices, MSpace::kWorld);

	for (unsigned int i=0; i < ExportMesh.Vertices.length(); ++i)
		ExportMesh.Vertices[i].cartesianize();

	Mesh.getNormals(ExportMesh.Normals, MSpace::kObject);
	Mesh.getTangents(ExportMesh.Tangents, MSpace::kObject);
	Mesh.getUVs(ExportMesh.U_Coords, ExportMesh.V_Coords);

	//get faces
	MItMeshPolygon itFace(Mesh.object());

	unsigned int tri_count = 0;
	unsigned int ngon_count = 0;
	unsigned int quad_count = 0;

	while (!itFace.isDone())
	{
		unsigned int vertex_count = itFace.polygonVertexCount();

		//skip ngons completely, we don't want these at all
		if (vertex_count > 4)
		{
			++ngon_count;

			itFace.next();
			continue;
		}
					
		//assume triangle, we'll do the rest later if this is a quad
		{
			TMayaExportTriangle tri = GetTriangle(itFace, 0, 1, 2);
			ExportMesh.Triangles.push_back(tri);

			++tri_count;
		}

		//if this is a quad we're missing one triangle now
		if (vertex_count == 4)
		{
			TMayaExportTriangle tri = GetTriangle(itFace, 0, 2, 3);
			ExportMesh.Triangles.push_back(tri);

			++quad_count;
			++tri_count;
		}

		itFace.next();	
	}
		
	if (g_TmVerbosity >= 1)
		cout << "Total triangles: " << tri_count << " (" << ngon_count << " ngons discarded, " << quad_count << " quads split)" << endl;

	target.AddMesh(ExportMesh);
}

void ExportCollisionMesh(TBaseMayaExportTarget& target, MFnMesh& Mesh)
{
	if (g_TmVerbosity >= 1)
		cout << "Found collision mesh object: " << Mesh.name().asChar() << endl;
	
	TMayaBaseMesh CollisionMesh;

	//copy the verts from the mesh
	Mesh.getPoints(CollisionMesh.Vertices, MSpace::kWorld);
	for (unsigned int i=0; i < CollisionMesh.Vertices.length(); ++i)
		CollisionMesh.Vertices[i].cartesianize();

	//copy the triangles

	MItMeshPolygon itFace(Mesh.object());
	unsigned int tri_count = 0;
	unsigned int ngon_count = 0;
	unsigned int quad_count = 0;

	while (!itFace.isDone())
	{
		unsigned int vertex_count = itFace.polygonVertexCount();

		//skip ngons completely, we don't want these at all
		if (vertex_count > 4)
		{
			++ngon_count;
			itFace.next();
			continue;
		}
					
		//assume triangle, we'll do the rest later if this is a quad
		{
			TMayaExportTriangle tri = GetTriangle(itFace, 0, 1, 2);
			CollisionMesh.Triangles.push_back(tri);

			++tri_count;
		}

		//if this is a quad we're missing one triangle now
		if (vertex_count == 4)
		{
			TMayaExportTriangle tri = GetTriangle(itFace, 0, 2, 3);
			CollisionMesh.Triangles.push_back(tri);

			++quad_count;
			++tri_count;
		}

		itFace.next();
			
	}
		
	if (g_TmVerbosity >= 1)
		cout << "Total triangles: " << tri_count << " ("<<ngon_count<<" ngons discarded, " << quad_count << " quads split)" << endl;
	
	target.AddCollisionMesh(CollisionMesh);
}

void ExportMesh(TBaseMayaExportTarget& target, MFnMesh& mesh, int meshno)
{
	//MObject meshobj = mesh.parent(0);
	//MFnMesh MeshFn = meshobj;
	MDagPath path = MDagPath::getAPathTo(mesh.object());
	MFnMesh mesh2(path);

	if (!mesh2.isIntermediateObject())
	{		
		//is this a collision mesh?
		if (mesh2.name().substring(0, 9) == "_collision")
			ExportCollisionMesh(target, mesh2);
		else
			ExportPolyMesh(target, mesh2);
	}
}

int GetJointParentIndex(MDagPathArray& joint_array, MDagPath& joint)
{
	int Result = -1;

	MFnIkJoint thisjoint(joint);
	
	for (unsigned int it = 0;
		it < joint_array.length() && Result == -1;
		++it)
	{
		MFnIkJoint otherjoint(joint_array[it]);

		if (thisjoint.parent(0) == otherjoint.object())
		{
			Result = it;
		}
		
		//if (MFnIkJoint(joint).isChildOf(MFnDagNode(joint_array[0]).object()))
		//	Result = it;
	}

	return Result;
}

/*namespace
{
	struct TWeightInfo
	{
		float Weights[3];
		int Influences[3];
	};
}*/

/*TMayaExportTriangle GetSkinnedTriangle(MItMeshPolygon& itFace, std::vector<TWeightInfo>& weightinfo, int v1, int v2, int v3)
{
	TMayaExportSkinnedTriangle tri;

	int points[] = {v1, v2, v3};
			
	for (int i=0; i < 3; ++i)
	{
		//get this tri's uvs
		int uv_index;
		itFace.getUVIndex(points[i], uv_index);

		//get vertex and normal indices
		int vert_index = itFace.vertexIndex(points[i]);
		int normal_index = itFace.normalIndex(points[i]);
		int tangent_index = itFace.tangentIndex(points[i]);

		tri.vertex_idx[i] = vert_index;
		tri.normal_idx[i] = normal_index;
		tri.uv_idx[i] = uv_index;
		tri.tangent_idx[i] = tangent_index;

		tri.influence_idx[i] = vert_index;
		tri.weight_idx[i] = vert_index;
	}

	return tri;
}

TMayaExportSkinnedMesh GetSkinnedMesh(MFnMesh& Mesh, std::vector<TWeightInfo>& weightinfo)
{
	TMayaExportSkinnedMesh ExportMesh;

	//get vertex positions
	Mesh.getPoints(ExportMesh.Vertices, MSpace::kWorld);

	for (unsigned int i=0; i < ExportMesh.Vertices.length(); ++i)
		ExportMesh.Vertices[i].cartesianize();

	Mesh.getNormals(ExportMesh.Normals, MSpace::kObject);
	Mesh.getTangents(ExportMesh.Tangents, MSpace::kObject);
	Mesh.getUVs(ExportMesh.U_Coords, ExportMesh.V_Coords);

	//get faces
	MItMeshPolygon itFace(Mesh.object());

	unsigned int tri_count = 0;
	unsigned int ngon_count = 0;
	unsigned int quad_count = 0;

	while (!itFace.isDone())
	{
		unsigned int vertex_count = itFace.polygonVertexCount();

		//skip ngons completely, we don't want these at all
		if (vertex_count > 4)
		{
			++ngon_count;

			itFace.next();
			continue;
		}
					
		//assume triangle, we'll do the rest later if this is a quad
		{
			TMayaExportTriangle tri = GetSkinnedTriangle(itFace, weightinfo, 0, 1, 2);
			ExportMesh.Triangles.push_back(tri);

			++tri_count;
		}

		//if this is a quad we're missing one triangle now
		if (vertex_count == 4)
		{
			TMayaExportTriangle tri = GetSkinnedTriangle(itFace, weightinfo, 0, 2, 3);
			ExportMesh.Triangles.push_back(tri);

			++quad_count;
			++tri_count;
		}

		itFace.next();	
	}
		
	if (g_TmVerbosity >= 1)
		cout << "Total triangles: " << tri_count << " (" << ngon_count << " ngons discarded, " << quad_count << " quads split)" << endl;
}*/

void ExportSkin(TBaseMayaExportTarget& target, MFnSkinCluster& skin)
{	
	TMayaExportSkin export_skin;
	export_skin.Name = skin.name();
	
	cout << "Exporting skin cluster: " << skin.name().asChar() << endl;
	
	MDagPathArray joints;
	skin.influenceObjects(joints);

	if (g_TmVerbosity > 0)
		cout << " *" << joints.length() << " joints in cluster." << endl;

	//get joints
	auto frame_counter = 0U;
	auto original_time = MAnimControl::currentTime();

	auto start_frame = MAnimControl::minTime().as(MTime::kPALFrame);
	auto end_frame = MAnimControl::maxTime().as(MTime::kPALFrame);

	for (auto frame_time = start_frame;
		frame_time < end_frame;
		++frame_time)
	{
		MAnimControl::setCurrentTime(MTime(frame_time, MTime::kPALFrame));

		TMayaExportSkin::TAnimFrame frame;
			
		for (unsigned int joint_it = 0;
			joint_it < joints.length();
			++joint_it)
		{
			MFnIkJoint jointfn(joints[joint_it]);
			MFnTransform jointxform (jointfn.object());

			//MMatrix jointmatrix = jointxform.transformation().asMatrix();
			MQuaternion jointrot;

			auto rotspace = joint_it==0? MSpace::kWorld : MSpace::kObject;

			jointfn.getRotationQuaternion(jointrot.x, jointrot.y, jointrot.z, jointrot.w, MSpace::kWorld);// rotspace);
			//jointxform.getRotation(jointrot, MSpace::kTransform);
			MFloatVector jointpos = jointfn.getTranslation(MSpace::kWorld);//MSpace::kTransform);

			MTransformationMatrix jointmatrix;
			//jointmatrix.addTranslation(jointpos, MSpace::kTransform);
			//jointmatrix.addRotationQuaternion(jointrot.x, jointrot.y, jointrot.z, jointrot.w, MSpace::kTransform);
			jointmatrix = jointfn.transformation();

			//MTransformationMatrix jointmatrix;
			//jointmatrix.addTranslation(jointpos, MSpace::kTransform);
			//MTransformationMatrix jointmatrix = jointxform.transformation().asMatrix();
			//jointmatrix.addTranslation(jointpos, MSpace::kTransform);
		
			if (g_TmVerbosity > 0)
				cout << "	" << jointfn.name().asChar() << " at [" << jointpos.x << ", " <<jointpos.y <<", " <<jointpos.z << "]" << endl;

			TMayaExportJoint export_joint;
			//export_joint.Transform = jointmatrix.asMatrix();
			export_joint.Position = jointpos;
			export_joint.Rotation = jointrot;
			export_joint.Parent = GetJointParentIndex(joints, joints[joint_it]);
			export_joint.Name = jointfn.name();
			
			frame.push_back(export_joint);
		}

		export_skin.Joints.push_back(frame);
		++frame_counter;
	}

	MAnimControl::setCurrentTime(original_time);

	//get weights the first affected mesh
	unsigned int meshindex = skin.indexForOutputConnection(0);

	MDagPath geo_path;
	skin.getPathAtIndex(meshindex, geo_path);

	MItGeometry geo_component_it(geo_path);

	if (g_TmVerbosity > 0)
	cout <<" *Mesh belonging to this skin: " <<MFnMesh(geo_path).name().asChar() <<" (" <<geo_component_it.count() <<" vertices)" <<endl;

	//export_skin.MeshName = MFnMesh(geo_path).name();

	//std::vector<TWeightInfo> export_weights;

	unsigned int vert_no = 0;
	for (; !geo_component_it.isDone(); geo_component_it.next())
	{
		MObject vertex = geo_component_it.component();
		
		MFloatArray weights;
		unsigned int influences;

		skin.getWeights(geo_path, vertex, weights, influences);

		if (g_TmVerbosity > 1)
		{
			cout <<"    *Vertex #" <<vert_no <<": [";
			for (unsigned int w_it = 0;
				w_it != weights.length();
				++w_it)
			{
				cout << weights[w_it] << ", ";
			}
			cout <<"]" <<endl;
		}

		TMayaExportWeight new_weight;

		//the weights from maya come in a big long list - one entry per bone in the skin
		//we need to break that down into a maximum of 3 weights, with corresponding bone IDs
		unsigned int this_vertex_weight_count = 0;
		for (unsigned int weight_it = 0;
			weight_it < weights.length();
			++weight_it)
		{
			//if this is a valid weight, and we haven't already got 3 weights
			if (weights[weight_it] > 0
				&& this_vertex_weight_count < TMayaExportSkin::MAX_INFLUENCES)
			{
				//memset(&new_weight, 0, sizeof(new_weight)); //hacky init to 0

				//use this_vertex_weight count as an index - it goes from 0..2
				new_weight.Influences[this_vertex_weight_count] = weight_it;
				new_weight.Values[this_vertex_weight_count] = weights[weight_it];

				//export_skin.Weights.push_back(new_weight);

				++this_vertex_weight_count;

				//int idx = used_weight_count;

				/*export_weight.Joints[idx] = weight_it;
				export_weight.Values[idx] = weights[weight_it];

				export_skin.Weights.push_back(export_weight);
				
				++used_weight_count;*/
			}
		}

		export_skin.Weights.push_back(new_weight);

		++vert_no;
	}

	//now get the poly mesh info for the skinned mesh
	if (g_TmVerbosity > 0)
		cout <<"Exporting skinned mesh...";



	target.AddSkin(export_skin);
}

void ExportLocator(TBaseMayaExportTarget& target, MObject& object)
{
	TMayaExportLocator export_locator;

	MFnDagNode locator(object);

	MDagPath parent;
	MFnDagNode(locator.parent(0)).getPath(parent);	

	export_locator.Name = locator.name();
	export_locator.Position = MFnTransform(parent).getTranslation(MSpace::kWorld);
	
	target.AddLocator(export_locator);

	cout << "Exporting locator: " + export_locator.Name << std::endl;
}

MStatus ExportMayaMeshes(TBaseMayaExportTarget& target)
{
	MStatus stat;
	
	MString Filename;
	stat = target.GetOutputPath(Filename);

	if (!stat)
	{
		cout << "ERROR: failed to get output path." << endl;
		cout << "Result was " << Filename.asChar() <<endl;
		return stat;
	}

	cout << "Got filename: " << Filename.asChar() << endl;
	
	MItDependencyNodes itNodes(MFn::kInvalid);

	MFnSkinCluster* skin_object = nullptr;

	unsigned int mesh_count = 0;

	for (; !itNodes.isDone(); itNodes.next())
	{
		MObject item = itNodes.item();

		switch (item.apiType())
		{
		case MFn::kMesh:
			{
				MFnMesh mesh(item);
				
				if (!mesh.isIntermediateObject())
 				{
					ExportMesh(target, mesh, mesh_count);
		 
					++mesh_count;
				}
			} break;
		case MFn::kSkinClusterFilter:
			{
				//only export 1 skinned object!
				if (!skin_object)
				{
					skin_object = new MFnSkinCluster(item);
				
					ExportSkin(target, *skin_object);
				}
			} break;
		case MFn::kLocator:
			{
				ExportLocator(target, item);				
			} break;
		default:
				break;
		}
	}

	if (skin_object)
	{
		delete skin_object;
	}
	
	cout << endl << "Finished loading " << mesh_count << " meshes." << endl;

	stat = target.SaveFile(Filename);
	return stat;
}

